// -----------------------------------------------------------
// raytracer.cpp
// 2004 - Jacco Bikker - jacco@bik5.com - www.bik5.com -   <><
// -----------------------------------------------------------

#include "raytracer.h"
#include "scene.h"
#include "common.h"
#include "windows.h"
#include "winbase.h"

namespace Raytracer {

Ray::Ray( vector3& a_Origin, vector3& a_Dir ) : 
	m_Origin( a_Origin ), 
	m_Direction( a_Dir )
{
}

Engine::Engine()
{
	m_Scene = new Scene();
}

Engine::~Engine()
{
	delete m_Scene;
}

// -----------------------------------------------------------
// Engine::SetTarget
// Sets the render target canvas
// -----------------------------------------------------------
void Engine::SetTarget( Pixel* a_Dest, int a_Width, int a_Height )
{
	// set pixel buffer address & size
	m_Dest = a_Dest;
	m_Width = a_Width;
	m_Height = a_Height;
}


//-------------------------------------------------
// Loops through all objhects to find if the defined pixel is a shadow or not
//
//-----------------------------------------------



// -----------------------------------------------------------
// Engine::Raytrace
// Naive ray tracing: Intersects the ray with every primitive
// in the scene to determine the closest intersection
// -----------------------------------------------------------
Primitive* Engine::Raytrace( Ray& a_Ray, Color& a_Acc, int a_Depth, float a_RIndex, float& a_Dist)
{
	if (a_Depth > TRACEDEPTH) return 0;
	// trace primary ray
	a_Dist = 1000000.0f;
	vector3 pi;
	Primitive* prim = 0;
	int result;
	// find the nearest intersection
	for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ )
	{
		Primitive* pr = m_Scene->GetPrimitive( s );
		int res;
		if (res = pr->Intersect( a_Ray, a_Dist )) 
		{
			prim = pr;
			result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive
		}
	}
	// no hit, terminate ray
	if (!prim) return 0;
	// handle intersection
	if (prim->IsLight())
	{
		// we hit a light, stop tracing
		a_Acc = Color( 1, 1, 1 );
	}
	else
	{
		// determine color at point of intersection
		pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist;

		// trace lights
		for (int l = 0; l < m_Scene->GetNrPrimitives(); l++)
		{
			Primitive* light = m_Scene->GetPrimitive(l);
			vector3 dir;
			float shade = CalcShade(light, pi, dir);


			// calculate diffuse shading
			vector3 L = ((Sphere*)light)->GetCentre() - pi;
			NORMALIZE(L);
			vector3 N = prim->GetNormal(pi);
			if (prim->GetMaterial()->GetDiffuse() > 0)
			{
				float dot = DOT(N, L);
				if (dot > 0)
				{
					Color reflectedColor = Color(0, 0, 0);
					float refl = prim->GetMaterial()->GetReflection();
					//adding the reflection
					if (a_Depth < TRACEDEPTH && refl > 0)
					{
						int nextDepth = a_Depth + 1;
						float depth;
						vector3 refDir = a_Ray.GetDirection() - 2.0f * DOT(a_Ray.GetDirection(), N) * N;
						Raytrace(Ray(pi + refDir * EPSILON, refDir), reflectedColor, nextDepth, 1.0f, depth);
						a_Acc += refl * reflectedColor * prim->GetMaterial()->GetColor();

					}
					/*for transparent materials not working */
					if (prim->GetMaterial()->GetRefractionIndex() > 0 && a_Depth < TRACEDEPTH)
					{
						float rindex = prim->GetMaterial()->GetRefractionIndex();
						float n = a_RIndex / rindex;
						vector3 N = prim->GetNormal(pi) * (float)result;
						float cosI = -DOT(N, a_Ray.GetDirection());
						float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);
						if (cosT2 > 0.0f)
						{
							vector3 T = (n * a_Ray.GetDirection()) + (n * cosI - sqrtf(cosT2)) * N;
							Color rcol(0, 0, 0);
							float dist;
							Raytrace(Ray(pi + T * EPSILON, T), rcol, a_Depth + 1, rindex, dist);
							a_Acc += rcol;
						}
					}

					//adding the diffuse part
					if (prim->GetMaterial()->GetDiffuse() > 0)
					{
						//adding the diffuse component 
						float diff = dot * prim->GetMaterial()->GetDiffuse() * shade;
						// add diffuse component to ray color
						a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor();
					}

					//adding the specular part 
					if (prim->GetMaterial()->GetSpecular() > 0)
					{
						//adding the specular component
						float spec = powf(dot, 50) * prim->GetMaterial()->GetSpecular() * shade;
						a_Acc += spec * light->GetMaterial()->GetColor();
					}
				}
			}
		}
	}
	return prim;
}

float Engine::CalcShade(Primitive* a_Light, vector3 a_Ip, vector3& a_Dir)
{
	if (a_Light->GetType() == Primitive::SPHERE)
	{
		float retVal = 1.0f;
		vector3 lightDir = ((Sphere*)a_Light)->GetCentre() - a_Ip;
		//normalize it
		float tdist = LENGTH(lightDir);
		lightDir *= (1.0f / tdist);
		Ray interToLight(a_Ip + lightDir * EPSILON, lightDir);

		for (int index = 0; index < m_Scene->GetNrPrimitives(); index++)
		{
			Primitive* currentPRimitive = m_Scene->GetPrimitive(index);
			float dist = 10000000;
			if (currentPRimitive != p && currentPRimitive->Intersect(interToLight, dist))
			{
				retVal = 0;
			}
		}

		return retVal;
	}
	else if (a_Light->GetType() == Primitive::AABB)
	{
		float retval = 0;
				break;
		Box* b = (Box*)a_Light;
		a_Dir = (b->GetPos() + 0.5f * b->GetSize()) - a_Ip;
		NORMALIZE(a_Dir);
		
		//if montecarlo sampling is equal to 1 then we need to do it
		#if MONTECARLOSAMPLING

		#else

		#endif
	}

}

// -----------------------------------------------------------
// Engine::InitRender
// Initializes the renderer, by resetting the line / tile
// counters and precalculating some values
// -----------------------------------------------------------
void Engine::InitRender()
{
	// set firts line to draw to
	m_CurrLine = 20;
	// set pixel buffer address of first pixel
	m_PPos = 20 * m_Width;
	// screen plane in world space coordinates
	m_WX1 = -4, m_WX2 = 4, m_WY1 = m_SY = 3, m_WY2 = -3;
	// calculate deltas for interpolation
	m_DX = (m_WX2 - m_WX1) / m_Width;
	m_DY = (m_WY2 - m_WY1) / m_Height;
	m_SY += 20 * m_DY;
	// allocate space to store pointers to primitives for previous line
	m_LastRow = new Primitive*[m_Width];
	memset( m_LastRow, 0, m_Width * 4 );
}

int GetRelativePosition(int arrayWidth, int currentPosition)
{
	int wholeNumber = currentPosition / arrayWidth;
	int reduceNumber = wholeNumber * arrayWidth;
	return currentPosition - reduceNumber;
}


// -----------------------------------------------------------
// Engine::Render
// Fires rays in the scene one scanline at a time, from left
// to right
// -----------------------------------------------------------
bool Engine::Render()
{

	Primitive** previousLinePrimitives = new Primitive*[m_Width];
	Color* colors = new Color[m_Width];


	for (int xIndex = 0; xIndex < m_Width; xIndex++)
	{
		colors[xIndex] = Color(0,0,0);
		previousLinePrimitives[xIndex] = NULL;
	}

	// render scene
	vector3 o( 0, 0, -5 );
	// initialize timer
	int msecs = GetTickCount();
	// reset last found primitive pointer
	Primitive* lastprim = 0;
	Primitive *previousPrimitve = NULL;
	// render remaining lines
	for ( int y = m_CurrLine; y < (m_Height - 20); y++ )
	{
		m_SX = m_WX1;
		// render pixels for current line
		for ( int x = 0; x < m_Width; x++ )
		{
			// fire primary ray
			Color acc( 0, 0, 0 );
			
			int red, blue, green;

			///*efficient anti aliasing*/
			vector3 dir = vector3(m_SX , m_SY , 0) - o;
			NORMALIZE(dir);
			Ray r(o, dir);
			float dist;
			Primitive* prim = Raytrace(r, acc, 1, 1.0f, dist);
			//relative position to the window
			int relativePos = GetRelativePosition(m_Width, m_PPos);

			//
			if (lastprim != prim)
			{
				lastprim = prim;
				Color acc(0, 0, 0);
				//subdiviiding a pixel  into 16 smmaller sub pixels 
				for (int tx = -1; tx < 2; tx++) for (int ty = -1; ty < 2; ty++)
				{
					vector3 dir = vector3(m_SX + m_DX * tx / 2.0f, m_SY + m_DY * ty / 2.0f, 0) - o;
					NORMALIZE(dir);
					Ray ray(o, dir);
					float dist;
					Primitive* prim = Raytrace(ray, acc, 1, 1.0f, dist);
				}

				red = (int)(acc.r * (256/9));
				green = (int)(acc.g * (256 / 9));
				blue = (int)(acc.b * (256 / 9));
			}
			else if (prim == NULL && previousLinePrimitives[relativePos] == NULL)
			{
				Color currentColor = colors[relativePos];
				if (acc.r != currentColor.r  || acc.b != currentColor.b  || acc.g != currentColor.g)
				{
					lastprim = prim;
					Color acc(0, 0, 0);
					//subdiviiding a pixel  into 16 smmaller sub pixels 
					for (int tx = -1; tx < 2; tx++) for (int ty = -1; ty < 2; ty++)
					{
						vector3 dir = vector3(m_SX + m_DX * tx / 2.0f, m_SY + m_DY * ty / 2.0f, 0) - o;
						NORMALIZE(dir);
						Ray ray(o, dir);
						float dist;
						Primitive* prim = Raytrace(ray, acc, 1, 1.0f, dist);
					}

					red = (int)(acc.r * (256 / 9));
					green = (int)(acc.g * (256 / 9));
					blue = (int)(acc.b * (256 / 9));
				}
				red = (int)(acc.r * 256);
				green = (int)(acc.g * 256);
				blue = (int)(acc.b * 256);
			}
			else
			{
				red = (int)(acc.r * 256);
				green = (int)(acc.g * 256);
				blue = (int)(acc.b * 256);
			}


			/*anti aliasing full blast*/
			/*for (int tx = 0; tx < 4; tx++)
			{
				for (int ty = 0; ty < 4; ty++)
				{
					vector3 dir = vector3(m_SX + m_DX * tx / 4.0f, m_SY + m_DY * ty / 4.0f, 0) - o;
					NORMALIZE(dir);
					Ray ray(o, dir);
					float dist;
					Primitive* prim = Raytrace(ray, acc, 1, 1.0f, dist);
				}
			}
			

			red = (int)(acc.r * 16);
			green = (int)(acc.g * 16);
			blue = (int)(acc.b * 16);*/


			if (red > 255) red = 255;
			if (green > 255) green = 255;
			if (blue > 255) blue = 255;

			
			m_Dest[m_PPos++] = (red << 16) + (green << 8) + blue;
			colors[relativePos] = Color(acc.r, acc.b, acc.g);
			previousLinePrimitives[relativePos] = prim;
			m_SX += m_DX;
		}

		m_SY += m_DY;
		// see if we've been working to long already
		if ((GetTickCount() - msecs) > 100) 
		{
			// return control to windows so the screen gets updated
			m_CurrLine = y + 1;
			return false;
		}
	}
	// all done
	return true;
}

}; // namespace Raytracer